#include "database_serializable.h"
#include "str.h"

smart_pointer<database> database_serializable::db;

void database_serializable::insert() const {
  string s;
  const size_t size = values.size();
  if (size != 0) {
    s = "INSERT INTO %1 (id,%2) VALUES (%3,%4)";

    string str_tmp_columns = global_empty_string;
    string str_tmp_values = global_empty_string;

    str_tmp_columns.reserve(size * 8);
    str_tmp_values.reserve(size * 8);

    const bool all_fields = all_fields_must_be_loaded();

    auto values_end_it = values.cend();
    for (auto it = values.cbegin(); it != values_end_it; ++it) {
      if (all_fields == false && it->second.column_must_be_loaded == false)
        continue;

      str_tmp_columns += it->first;
      str_tmp_columns += ',';

      if (it->second.content.empty() == false) {
        str_tmp_values += '\'';
        str_tmp_values += it->second.content;
        str_tmp_values += "\',";
      } else
        str_tmp_values += "NULL,";

    } // for

    str::trim(str_tmp_columns, ","); // removes the last ','
    str::trim(str_tmp_values, ","); // removes the last ','

    str::format(s, arg<const char*, string, unsigned long long, string>(class_name, str_tmp_columns, id, str_tmp_values));
  } else {
    s = "INSERT INTO %1 (id) VALUES (%2)";
    str::format(s, arg<const char*, unsigned long long>(class_name, id));
  } // else

  db->execute_update(s);
}

void database_serializable::update() const {
  const size_t size = values.size();
  if (size == 0)
    return;

  string s = "UPDATE %1 SET %2 WHERE id=%3";
  string str_tmp = global_empty_string;

  str_tmp.reserve(size * 8);

  const bool all_fields = all_fields_must_be_loaded();

  auto values_end_it = values.cend();
  for (auto it = values.cbegin(); it != values_end_it; ++it) {
    if (all_fields == false && it->second.column_must_be_loaded == false)
      continue;

    str_tmp += it->first;
    str_tmp += '=';

    if (it->second.content.empty() == false) {
      str_tmp += '\'';
      str_tmp += it->second.content;
      str_tmp += '\'';
    } else
      str_tmp += "NULL";

    str_tmp += ',';
  } // for

  str::trim(str_tmp, ","); // removes the last ','

  str::format(s, arg<const char*, string, unsigned long long>(class_name, str_tmp, id));
  db->execute_update(s);
}

void database_serializable::protocol() const {
  if (protocol_changes == false || loaded == false || rs == 0)
    return;

  string sql_statement;
  map<string, column_content>::const_iterator it;
  for (unsigned int j = 0; j < rs->get_column_count(); ++j) {
    const char *old_value = (*rs)(0U, j);

    it = values.find(rs->get_column_name(j));
    if (it == values.end())
      continue; // not found; this should not happen

    if (old_value == 0)
      old_value = "";

    if (rs->get_length(0U, j) != it->second.content.length() || it->second.content.compare(old_value) != 0) {
      if (it->second.content.empty() == true && rs->get_length(0U, j) == 0)
        sql_statement = "INSERT INTO protocol_table(old_value, new_value, table_name, column_name) VALUES (NULL, NULL, '%3', '%4')";
      else if (it->second.content.empty() == true)
        sql_statement = "INSERT INTO protocol_table(old_value, new_value, table_name, column_name) VALUES ('%1', NULL, '%3', '%4')";
      else if (rs->get_length(0U, j) == 0)
        sql_statement = "INSERT INTO protocol_table(old_value, new_value, table_name, column_name) VALUES (NULL, '%2', '%3', '%4')";
      else
        sql_statement = "INSERT INTO protocol_table(old_value, new_value, table_name, column_name) VALUES ('%1', '%2', '%3', '%4')";

      str::format(sql_statement, arg<const char*, string, const char*, const char*>(old_value, it->second.content, class_name, rs->get_column_name(j)));

      db->execute_update(sql_statement);
    } // if
  } // for j
}

bool database_serializable::all_fields_must_be_loaded() const {
  for (auto it = values.cbegin(); it != values.cend(); ++it) {
    if (it->second.column_must_be_loaded == true)
      return false;

  } // for

  return true;
}

void database_serializable::serialize(const string &variable_name, const bool value) {
  converter::to_string(value, values[variable_name].content); // no escape_apostrophe is required
}

void database_serializable::serialize(const string &variable_name, const int value) {
  converter::to_string(value, values[variable_name].content); // no escape_apostrophe is required
}

void database_serializable::serialize(const string &variable_name, const long long value) {
  converter::to_string(value, values[variable_name].content); // no escape_apostrophe is required
}

void database_serializable::serialize(const string &variable_name, const double value) {
  converter::to_string(value, values[variable_name].content); // no escape_apostrophe is required
}

void database_serializable::serialize(const string &variable_name, const string &value, bool escape_apostrophe) {
  if (escape_apostrophe == true) // makes sure that no sql injection happens
    converter::replace_all(value, "'", "\\'", values[variable_name].content);
  else
    values[variable_name].content = value;
}

void database_serializable::deserialize(bool &output, const string &variable_name) {
  string str_tmp;
  deserialize(str_tmp, variable_name);

  if (str::equals(str_tmp, "true", false) == true || str_tmp == "1" || str::equals(str_tmp, "t", false) == true)
    output = true;
  else
    output = false;
}

void database_serializable::deserialize(int &output, const string &variable_name) {
  string str_tmp;
  deserialize(str_tmp, variable_name);
  output = converter::to_int(str_tmp);
}

void database_serializable::deserialize(long long &output, const string &variable_name) {
  string str_tmp;
  deserialize(str_tmp, variable_name);
  output = converter::to_long_long(str_tmp);
}

void database_serializable::deserialize(double &output, const string &variable_name) {
  string str_tmp;
  deserialize(str_tmp, variable_name);
  output = converter::to_double(str_tmp);
}

void database_serializable::deserialize(string &output, const string &variable_name) {
  output.clear();

  if (loaded == false)
    database_serializable::load();

  if (rs->get_row_count() == 0)
    return;

  const unsigned int index = rs->get_column_index(variable_name.c_str());
  if (index == UINT_MAX) {
    if (all_fields_must_be_loaded() == false)
      return; // ignore unregistered columns, instead of throwing an exception

    string str_tmp = "Column name '%1' could not be found!";
    str::format(str_tmp, arg<string>(variable_name));
    throw db_exception(str_tmp);
  } // if

  const char *p = (*rs)(row, index);
  if (p != 0)
    output.assign(p, rs->get_length(row, index)); // do not use operator= here, call assign instead; assign can even save the binary data into an std::string
}

void database_serializable::load() {
  if (id_set == false)
    throw db_exception("Id is not set yet!");

  string s;

  if (all_fields_must_be_loaded() == true)
    s = "SELECT * FROM %1 WHERE id=%2";
  else {
    string str_tmp_columns = global_empty_string;

    for (auto it = values.cbegin(); it != values.cend(); ++it) {
      if (it->second.column_must_be_loaded == false)
        continue;

      str_tmp_columns += it->first;
      str_tmp_columns += ',';
    } // for

    str::trim(str_tmp_columns, ",");

    if (str_tmp_columns.empty() == true)
      throw db_exception("No column is defined to be loaded!");

    s = "SELECT ";
    s += str_tmp_columns;
    s += " FROM %1 WHERE id=%2";
  } // else

  str::format(s, arg<const char*, unsigned long long>(class_name, id));

  if (rs == 0)
    rs = new record_set();

  db->execute_query(s, *rs);
  loaded = true;
}

void database_serializable::persist(bool requires_lock) {
  record_set tmp_rs;

  try {
    if (requires_lock == true)
      db->lock_table(class_name);

    if (id_set == false) { // insert with a new id
      string s = "SELECT MAX(id) FROM %1";
      str::format(s, arg<const char*>(class_name));

      db->execute_query(s, tmp_rs);

      if (tmp_rs.get_row_count() == 1) {
        const char *p = tmp_rs(0, (unsigned int) 0);
        if (p != 0) {
          const unsigned long long id = converter::to_unsigned_long_long(p);
          if (id == ULLONG_MAX)
            throw parameter_exception("Id overflow, maximum of 2^64 is reached!");

          this->set_id(id + 1ULL);
        } else
          this->set_id(0ULL);
      } else
        this->set_id(0ULL);

      insert();
    } else { // insert or update with the given id
      string s = "SELECT 1 FROM %1 WHERE id=%2";
      str::format(s, arg<const char*, unsigned long long>(class_name, id));

      db->execute_query(s, tmp_rs);

      if (tmp_rs.get_row_count() == 1) { // id exists in the database
        update();
        protocol();
      } else if (tmp_rs.get_row_count() == 0) // id does not exist in the database
        insert();

    } // else

    if (requires_lock == true)
      db->unlock_table(class_name);

    values.clear(); // because of efficiency reasons
    loaded = false;
  } catch (const general_exception &) {
    if (requires_lock == true)
      db->unlock_table(class_name);

    values.clear();
    loaded = false;
    throw;
  } // catch
}

void database_serializable::remove() {
  if (id_set == false)
    throw db_exception("Id is not set yet!");

  string s = "DELETE FROM %1 WHERE id=%2";
  str::format(s, arg<const char*, unsigned long long>(class_name, id));
  db->execute_update(s);
  loaded = false;
}

database_serializable::database_serializable(const char * const class_name, bool protocol_changes) : serializable(class_name), id(ULLONG_MAX), rs(0), row(0), id_set(false), protocol_changes(protocol_changes) {
  // nothing to do
}

database_serializable::~database_serializable() {
  delete rs;
  rs = 0;
}

bool database_serializable::is_connected() {
  return (db.is_null() == false && db->is_connected() == true);
}

void database_serializable::set_database(database *db) {
  database_serializable::db = db;
}

safe_pointer<database> database_serializable::get_database() {
  return db.operator->();
}

void database_serializable::set_id(unsigned long long id) {
  if (this->id != id) {
    this->id = id;
    loaded = false;
  } // if

  id_set = true;
}

unsigned long long database_serializable::get_id() const {
  return this->id;
}

bool database_serializable::is_null(const string &column_name) {
  if (loaded == false)
    database_serializable::load();

  if (rs->get_row_count() == 0)
    return true;

  const unsigned int index = rs->get_column_index(column_name.c_str());
  if (index == UINT_MAX) {
    string str_tmp = "Column name '%1' could not be found!";
    str::format(str_tmp, arg<string>(column_name));
    throw db_exception(str_tmp);
  } // if

  const char *p = (*rs)(row, index);
  return (p == 0);
}

void database_serializable::clear_all_registered_columns() {
  for (map<string, column_content>::iterator it = values.begin(); it != values.end(); ++it)
    it->second.column_must_be_loaded = false;
}

void database_serializable::register_column(const string &column_name) {
  values[column_name].column_must_be_loaded = true;
}

void database_serializable::unregister_column(const string &column_name) {
  map<string, column_content>::iterator it = values.find(column_name);
  if (it != values.end())
    it->second.column_must_be_loaded = false;
}
